package tinybase.pf;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

import junit.framework.Assert;

import tinybase.basic.BytePointer;
import tinybase.basic.RC;
//
//PF_FileHandle
//
//Desc: Default constructor for a file handle object
//  A File object provides access to an open file.
//  It is used to allocate, dispose and fetch pages.
//  It is constructed here but must be passed to PF_Manager.OpenFile() in
//  order to be used to access the pages of a file.
//  It should be passed to PF_Manager.CloseFile() to close the file.
//  A file handle object contains a pointer to the file data stored
//  in the file table managed by PF_Manager.  It passes the file's unix
//  file descriptor to the buffer manager to access pages of the file.
//
public class PF_FileHandle {
	
	//private FileChannel fc = null;
	private PF_BufferMgr bufferMgr = null;
	private PF_FileHdr hdr = null;
	private boolean fileOpen = false;
	private boolean hdrChanged = false;
	private RandomAccessFile _raf = null;
	
	public void setRandomAccessFile(RandomAccessFile _abc) {
		_raf = _abc;
	}
	

	//
	//	getBufferMgr
	//	 
	//	Desc: get the instance bufferMgr of this class
	//	In:   Nothing
	//	Ret:  BufferMgr
	//
	public PF_BufferMgr getBufferMgr() {
		return bufferMgr;
	}

	//
	//	setBufferMgr
	//	 
	//	Desc: change the instance bufferMgr in this class
	//	In:   BufferMgr
	//	Ret:  Nothing
	//
	public void setBufferMgr(PF_BufferMgr bufferMgr) {
		this.bufferMgr = bufferMgr;
	}

	//
	//	isHdrChanged
	//	 
	//	Desc: get the value of hdrChanged
	//		  to see if the header of the file is changed or not.
	//	In:   Nothing
	//	Ret:  TRUE or FALSE
	//
	public boolean isHdrChanged() {
		return hdrChanged;
	}

	//
	//	setHdrChanged
	//	 
	//	Desc: set the value of hdrChanged 
	//	      to indicate if the header of the file is changed or not.
	//	In:   TRUE or FALSE
	//	Ret:  Nothing
	//
	public void setHdrChanged(boolean hdrChanged) {
		this.hdrChanged = hdrChanged;
	}
	
	//
	//  getFileHeader
	// 
	//	Desc: get the header of the file. 
	//	In:   Nothing
	//	Ret:  hdr
	//	
	public PF_FileHdr getFileHeader() {
		return this.hdr;
	}
	
	public PF_FileHandle(PF_BufferMgr bufferMgr, PF_FileHdr hdr,
			boolean fileOpen, boolean hdrChanged, RandomAccessFile fd) {

		this.bufferMgr = bufferMgr;
		this.hdr = hdr;
		this.setFileOpen(fileOpen);
		this.hdrChanged = hdrChanged;
		this._raf = fd;
	}
	
	public PF_FileHandle() {
		this.bufferMgr = null;
		this._raf = null;
		this.fileOpen = false;
		this.hdr = new PF_FileHdr(PF_Internal.PF_PAGE_LIST_END, 0);
		this.hdrChanged = false;
	}
	
	//
	// getFirstPage
	//
	// Desc: Get the first page in a file
    // 		 The file handle must refer to an open file
	// Out:  PageHandle - becomes a handle to the first page of the file
    //       The referenced page is pinned in the buffer pool.
	//       In Java, here we use the class PageHandle to replace the reference in C++.
	// Ret:  PF return code
	//	
	public RC getFirstPage(PF_PageHandle phd) {
		return (this.getNextPage(-1, phd));
	}
	
	//
	// getNextPage
	//
	// Desc: Get the next (valid) page after current
	//       The file handle must refer to an open file
	// In:   current - get the next valid page after this page number
	//       current can refer to a page that has been disposed
	// Out:  pageHandle - becomes a handle to the next page of the file
	//       The referenced page is pinned in the buffer pool.
	// Ret:  PF_EOF, or another PF return code
	//	
	public RC getNextPage(int current, PF_PageHandle phd) {
		RC rc;
		
		if (!this.isFileOpen())
			return RC.PF_CLOSEDFILE;
		
		if (current != -1 && !this.isValidPageNum(current)) {
			return RC.PF_INVALIDPAGE;
		}
		
		int iCurrent = current;
		int iNumPages = this.hdr.getNumPages();
		for (iCurrent++; iCurrent < iNumPages; iCurrent++) {
			rc = this.getThisPage(iCurrent, phd);
			if (rc == RC.SUCCESS) {
				return RC.SUCCESS;
			}
			
			if (rc != RC.PF_INVALIDPAGE) {
				return rc;
			}
		}
		
		return RC.PF_EOF;
	}

	//
	// getThisPage
	//
	// Desc: Get a specific page in a file
	//	     The file handle must refer to an open file
	// In:   pageNum - the number of the page to get
	// Out:  PageHandle - becomes a handle to the this page of the file
	//	     this function modifies local var's in pageHandle
	//	     The referenced page is pinned in the buffer pool.
	// Ret:  PF return code
	//	
	public RC getThisPage(int pageNum, PF_PageHandle phd) {
		RC rc;
// zliu 100		
		if (!this.isFileOpen()) {
System.out.println("getThisPage file is closed!");
			return RC.PF_CLOSEDFILE;
		}
		
		if (!this.isValidPageNum(pageNum)) {
			return RC.PF_INVALIDPAGE;
		}
		
		BytePointer bufWrapper = new BytePointer();
		rc = this.bufferMgr.getPage(
				this._raf,
				new PageNum(pageNum), 
				bufWrapper, 
				true);
		if (rc != RC.SUCCESS)
			return rc;
		
		int val = this.getNextFree(bufWrapper.getArray());
		if (val == PF_Internal.PF_PAGE_USED) {
			phd.setPageNum(pageNum);
			/**
			 * Here, we can't do as in C++
			 * we return an array starting with page header
			 */
			phd.setData(bufWrapper.getArray());
			return RC.SUCCESS;
		}
		
		if ((rc = this.unpinPage(pageNum)) != RC.SUCCESS) {
			return rc;
		}
		
		return RC.PF_INVALIDPAGE;
	}

	//
	// GetLastPage
	//
	// Desc: Get the last page in a file
	//	     The file handle must refer to an open file
	// Out:  pageHandle - becomes a handle to the last page of the file
	//	     The referenced page is pinned in the buffer pool.
	// Ret:  PF return code
	//
	public RC getLastPage(PF_PageHandle phd) {
		return this.getPrevPage(hdr.getNumPages(), phd);
	}

	//
	// getPrevPage
	//
	// Desc: Get the prev (valid) page after current
	//	     The file handle must refer to an open file
	// In:   current - get the prev valid page before this page number
	//	     current can refer to a page that has been disposed
	// Out:  pageHandle - becomes a handle to the prev page of the file
	//	     The referenced page is pinned in the buffer pool.
	// Ret:  PF_EOF, or another PF return code
	//
	public RC getPrevPage(int current, PF_PageHandle phd) {
		RC rc;
		
		if (!this.isFileOpen())
			return RC.PF_CLOSEDFILE;
		
		if (current != hdr.getNumPages()
				&& !this.isValidPageNum(current)) {
			return RC.PF_INVALIDPAGE;
		}
		
		int iCurrent = current;
		for(iCurrent--; iCurrent >= 0; iCurrent--) {
			rc=this.getThisPage(iCurrent, phd);
			if (rc == RC.SUCCESS) {
				return RC.SUCCESS;
			}
			
			if (rc != RC.PF_INVALIDPAGE) {
				return rc;
			}
		}
		
		return RC.PF_EOF;
	}

	public int getNextFree(byte [] pageBuf) {
		ByteBuffer buf = ByteBuffer.wrap(pageBuf);
		int value = buf.getInt(0);
		return value;
	}

	//
	// allocatePage
	//
	// Desc: Allocate a new page in the file (may get a page which was
	//	     previously disposed)
	//	     The file handle must refer to an open file
	// Out:  pageHandle - becomes a handle to the newly-allocated page
	//	     this function modifies local var's in pageHandle
	// Ret:  PF return code
	//
	public RC allocatePage(PF_PageHandle phd) {
		if (!this.isFileOpen()) {
			return RC.PF_CLOSEDFILE;
		}
		
		PageNum refPageNum = new PageNum(PageNum.PF_PAGE_UNSET);
		RC rc;
		BytePointer ptr = new BytePointer();
		// If the free list isn't empty...
		if (this.hdr.getFirstFree() != PF_Internal.PF_PAGE_LIST_END) {
			refPageNum.setPageNum(this.hdr.getFirstFree());
			rc = this.bufferMgr.getPage(this._raf, refPageNum, ptr, true);
			if (rc != RC.SUCCESS)
				return rc;
			this.hdr.setFirstFree(getNextFree(ptr.getArray()));
		}
		else {
			int numPages = hdr.getNumPages();
			refPageNum.setPageNum(numPages);
			rc = this.bufferMgr.allocatePage(this._raf, refPageNum, ptr);
			if (rc != RC.SUCCESS)
				return rc;
			this.hdr.setNumPages(numPages + 1);
		}
		
		this.hdrChanged = true;
		byte[] arr = ptr.getArray();
//		System.err.println("the bufferMgr.allocatePage return a buffer of length " + arr.length);
		java.util.Arrays.fill(arr, (byte)0);
		ByteBuffer buf = ByteBuffer.wrap(arr);
		buf.putInt(0, PF_Internal.PF_PAGE_USED);
		
		
		rc = this.markDirty(refPageNum.getPageNum());
		if (rc != RC.SUCCESS) {
			return rc;
		}
		
		phd.setPageNum(refPageNum.getPageNum());
		phd.setData(arr);
		return RC.SUCCESS;
	}

	//
	// disposePage
	//
	// Desc: Dispose of a page
	//	     The file handle must refer to an open file
	//	     PF_PageHandle objects referring to this page should not be used
	//	     after making this call.
	// In:   pageNum - number of page to dispose
	// Ret:  PF return code
	//	
	public RC disposePage(int pageNum) {
		
		RC rc;
		BytePointer ptr = new BytePointer();
		
		if (!this.isFileOpen()) {
			return RC.PF_CLOSEDFILE;
		}
		
		if (!this.isValidPageNum(pageNum)) {
			return RC.PF_INVALIDPAGE;
		}
		if ((rc = this.bufferMgr.getPage(
				this._raf,
				new PageNum(pageNum),
				ptr,
				false)) != RC.SUCCESS) {
			return rc;
		}
		
		if (this.getNextFree(ptr.getArray()) != PF_Internal.PF_PAGE_USED) {
			if((rc = this.unpinPage(pageNum)) != RC.SUCCESS) {
				return rc;
			}
		
			return RC.PF_PAGEFREE;
		
		}
		
		//this.getHdr(ptr.addr).nextFree = hdr.firstFree;
		ByteBuffer buf = ByteBuffer.wrap(ptr.getArray());
		buf.putInt(0, hdr.getFirstFree());
		hdr.setFirstFree(pageNum);
		this.hdrChanged = true;
		
		if ((rc = this.markDirty(pageNum)) != RC.SUCCESS ) {
			return rc;
		}
		
		if ((rc = this.unpinPage(pageNum)) != RC.SUCCESS ) {
			return rc;
		}
				
		return RC.SUCCESS;
	}

	//
	// markDirty
	//
	// Desc: Mark a page as being dirty
	//	     The page will then be written back to disk when it is removed from
	//	     the page buffer
	//	     The file handle must refer to an open file
	// In:   pageNum - number of page to mark dirty
	// Ret:  PF return code
	//
	public RC markDirty(int pageNum) {
		
		if (!this.isFileOpen())
			return RC.PF_CLOSEDFILE;
		
		if (!this.isValidPageNum(pageNum))
			return RC.PF_INVALIDPAGE;
		
		return this.bufferMgr.markDirty(this._raf, new PageNum(pageNum));
	}

	//
	// unpinPage
	//
	// Desc: Unpin a page from the buffer manager.
	//	     The page is then free to be written back to disk when necessary.
	//	     PF_PageHandle objects referring to this page should not be used
	//	     after making this call.
	//	     The file handle must refer to an open file.
	// In:   pageNum - number of the page to unpin
	// Ret:  PF return code
	//
	public RC unpinPage(int pageNum) {
		if (!this.isFileOpen())
			return RC.PF_CLOSEDFILE;
		
		if (!this.isValidPageNum(pageNum))
			return RC.PF_INVALIDPAGE;
		
		return this.bufferMgr.unpinPage(this._raf, new PageNum(pageNum));
	}

	//
	//	writeFileHdr
	//
	//	Desc: Write a FileHdr which includes two parts:
	//		  the first free page in this file 
	//		  and the total number of all the pages in this file
	//	In:	  Nothing
	//	Ret:  PF return code
	//
	public RC writeFileHdr() {
		byte[] buf = new byte[8];
		ByteBuffer bbuf = ByteBuffer.wrap(buf);
		bbuf.putInt(0, this.hdr.getFirstFree());
		bbuf.putInt(4, this.hdr.getNumPages());
		try {
			_raf.seek(0);
			_raf.write(buf);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return RC.SUCCESS;
	}

	//
	// flushPages
	//
	// Desc: Flush all dirty unpinned pages from the buffer manager for this file
	// In:   Nothing
	// Ret:  PF_PAGEFIXED warning from buffer manager if pages are pinned or
	//	     other PF error
	//
	public RC flushPages () {
		RC rc;
		
		if (!this.isFileOpen()) {
			return RC.PF_CLOSEDFILE;
		}
		
		if (this.hdrChanged) {
			rc = this.writeFileHdr();
			if (rc != RC.SUCCESS) {
				return rc;
			}
			this.hdrChanged = false;
		}
		
		return this.bufferMgr.flushPages(this._raf);
	}

	//
	// forcePages
	//
	// Desc: If a page is dirty then force the page from the buffer pool
	//	     onto disk.  The page will not be forced out of the buffer pool.
	// In:   The page number, a default value of ALL_PAGES will be used if
	//	     the client doesn't provide a value.  This will force all pages.
	// Ret:  Standard PF errors
	//
	//
	public RC forcePages (int pageNum) {
		
		RC rc;
		
		if (!this.isFileOpen())
			return RC.PF_CLOSEDFILE;
		
		if(this.hdrChanged){
			rc = this.writeFileHdr();
			if (rc != RC.SUCCESS) {
				return rc;
			}
			this.hdrChanged = false;
		}
		
		
		return this.bufferMgr.forcePages(this._raf, new PageNum(pageNum));
	}
	
	// Private members
	//
	// isValidPageNum
	//
	// Desc: Internal.  Return TRUE if pageNum is a valid page number
	//	     in the file, FALSE otherwise
	// In:   pageNum - page number to test
	// Ret:  TRUE or FALSE
	//
	private boolean isValidPageNum(int pageNum) {
		return (this.isFileOpen() &&
				pageNum >=0 && 
				pageNum < this.hdr.getNumPages());
	}
	
	public RandomAccessFile getRandomAccessFile() {
		return this._raf;
	}

	/**
	 * @param fileOpen the fileOpen to set
	 */
	public void setFileOpen(boolean fileOpen) {
		this.fileOpen = fileOpen;
	}

	/**
	 * @return the fileOpen
	 */
	public boolean isFileOpen() {
		return fileOpen;
	}
	
}
